module table;

import std.conv;
import std.stdio;
import std.string;
import std.algorithm;

/*************
 * Available Types
 *************/
enum DataType : int
{
  bool_t,
  int_t,
  real_t,
  string_t
}

string toStr(DataType d) {
  final switch(d) {
    case DataType.bool_t:   return "bool";
    case DataType.int_t:    return "int";
    case DataType.real_t:   return "real";
    case DataType.string_t: return "string";
  }
}

DataType fromStr(string s) {
  switch(s) {
    case "bool":   return DataType.bool_t;
    case "int":    return DataType.int_t;
    case "real":   return DataType.real_t;
    case "string": return DataType.string_t;
    default:
      writeln("Internal error: " ~ s ~ " is not a valid type.");         
      return DataType.int_t;
  }
}

/************
 * Base class of all entries.
 ************/
class EntryBase
{
  this(DataType type_) {type = type_;}

  abstract string toStringRepr() const;

  bool opEquals(const ref string rhs) const {
    return toStringRepr() == rhs;
  }

  abstract int opCmp(ref const string) const;

private:
  DataType type;
}

/************
 * Tool for operator overloading
 ************/
bool isMathOp(const string op)
{
  return op == "+" || op == "-" || op == "*" || op == "/";
}

/************
 * Entry types
 ************/

class BoolEntry : EntryBase
{
  this(string str) {
    super(DataType.bool_t);
    value = (str=="true"?true:false);
  }

  override string toStringRepr() const {
    return value?"true":"false";
  }

  override int opCmp(ref const string rhs) const {
    return value - (rhs=="true"?true:false);
  }

private:
  bool value;
}

class IntEntry : EntryBase
{
  this(string str) { super(DataType.int_t); value = parse!int(str); }

  override string toStringRepr() const {
    return text(value);
  }

  override int opCmp(ref const string rhs) const {
    auto tmp = rhs.dup;
    return value - parse!int(tmp);
  }

  override int opBinary(string op)(int i) if(isMathOp(op)) {
    mixin("return value" ~ op ~ "i;");
  }

private:
  int value;
}

class RealEntry : EntryBase
{
  this(string str) { super(DataType.real_t); value = parse!double(str); }

  override string toStringRepr() const {
    return text(value);
  }

  override int opCmp(ref const string rhs) const {
    auto tmp = rhs.dup;
    return cast(int)(value - parse!double(tmp));
  }

  override double opBinary(string op)(double i) if(isMathOp(op)) {
    mixin("return value" ~ op ~ "i;");
  }

private:
  double value;
}

class StringEntry : EntryBase
{
  this(string str) { super(DataType.string_t); value = str; }

  override string toStringRepr() const {
    return value;
  }

  override int opCmp(ref const string rhs) const {
    return icmp(value, rhs);
  }

private:
  string value;
}

/************
 * Factory for creating entries
 ************/
EntryBase EntryFactory(string str, DataType type) {
    final switch(type) {
      case DataType.bool_t:
        return new BoolEntry(str);
      case DataType.int_t:
        return new IntEntry(str);
      case DataType.real_t:
        return new RealEntry(str);
      case DataType.string_t:
        return new StringEntry(str);
    }
}

/************
 * Column from one type of entries
 ************/
class Header
{
  string name;
  DataType type;
  bool optional;

  this(string input) {
    auto tokens = splitLines(tr(input,"|","\n"));
    name = tokens[0];
    type = fromStr(tokens[1]);
    optional = (tokens[2] == "true");
  }

  string toStringRepr() {
    return name ~ "|" ~ toStr(type) ~ "|" ~ (optional?"true":"false");
  }
}

class Column
{
  this(Header h) {
    header = h;
  }

  Header getHeader() {
    return header;
  }

  void put(EntryBase e) {
    data ~= e;
  }
 
  EntryBase opIndex(size_t i) {
    return data[i];
  }

  void delEntries(size_t[] idx) {
    foreach_reverse(i; idx)
      data = remove(data, i);
  }

  void resize(size_t s) {
    data.length = s;
  }

private:
  Header header;
  EntryBase[] data;
}

/************
 * Table from columns
 ************/
alias EntryBase[] Row;

class Table
{
  this(string name_) {
    rows = 0;
    name = name_;
  }
 
  Column[] getColumns() {
    return columns;
  }

  void addColumn(Column c) {
    columns ~= c;
    columns[$-1].resize(rows);
  }

  void removeColumn(size_t idx) {
    columns = remove(columns, idx);
  }

  string getName() { return name; }

  bool addRow(Row entries) {
    foreach(int i, EntryBase e; entries)
      columns[i].put(e);

    ++rows;
    return true;
  }

  Row[] getRows(bool delegate(Row) filter) {
    Row[] matches;
    for(size_t i = 0; i < rows; ++i) {
      Row r;
      foreach(Column c; columns)
        r ~= c[i];
      if (filter(r))
        matches ~= r;
    }
	return matches;
  }

  size_t deleteRows(bool delegate(Row) filter) {
    size_t[] to_remove;
    for(size_t i = 0; i < rows; ++i) {
      Row r;
      foreach(Column c; columns)
        r ~= c[i];
      if (filter(r))
        to_remove ~= i;
    }
    auto removed = to_remove.length;
    foreach(Column c; columns)
      c.delEntries(to_remove);
    rows -= removed;
    return removed;
  }

private:
  string name;
  Column[] columns;
  size_t rows;
}

